package Utils;

import org.apache.poi.xwpf.usermodel.*;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author YuanX
 * @version 1.0
 * @date 2022/09/13
 */
public class DocxReplaceUtil {
    /**
     * 用一个docx文档作为模板，然后替换其中的内容，再写入目标文档中。
     *
     * @param response
     * @param params   导出参数
     * @param path     模板文件未知
     * @param fileName 文件名称
     * @throws Exception
     */
    public void replaceDefineVar( Map<String, Object> params, String path) throws IOException {


        InputStream is = new FileInputStream(path);

        XWPFDocument doc = new XWPFDocument(is);

//        //插入表格
//        for (Map.Entry<String, Object> entry : params.entrySet()) {
//            if (entry.getKey().equals("expertGroup")) {
//                doc = new InsertExcelByKeyInWord().useTables(doc, "expertGroup", (List<JSONObject>) entry.getValue());
//            }
//        }

        //替换段落里面的变量
        this.replaceInParass(doc, params);

        //替换表格里面的变量
        this.replaceInTable(doc, params);

        //写入生成docx文件
        OutputStream os = new FileOutputStream("D:\\B.docx");
        doc.write(os);
        this.close(os);
        this.close(is);

//        OutputStream fos = response.getOutputStream();
//        response.setCharacterEncoding("UTF-8");
//        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
//        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
//        response.setHeader("Content-Disposition", "attachment;filename= " + fileName + ".docx");
//        doc.write(fos);
//        fos.close();
    }

    /**
     * 替换段落里面的变量
     *
     * @param doc    要替换的文档
     * @param params 参数
     */
    private void replaceInPara(CustomXWPFDocument doc, Map<String, Object> params) {
        for (XWPFParagraph para : doc.getParagraphs()) {
            this.replaceInParas(para, params);
        }
    }

    /**
     * 替换段落里面的变量
     *
     * @param doc    要替换的文档
     * @param params 参数
     */
    private void replaceInParass(XWPFDocument doc, Map<String, Object> params) {
        for (XWPFParagraph para : doc.getParagraphs()) {
            this.replaceInParas(para, params);
        }
    }


    /**
     * 替换段落里面的变量
     *
     * @param para   要替换的段落
     * @param params 参数
     */
    private void replaceInPara(XWPFParagraph para, Map<String, Object> params) {
        List<XWPFRun> runs;
        Matcher matcher;
        int fontSize = 0;
        UnderlinePatterns underlinePatterns = null;
        this.replaceText(para);//如果para拆分的不对，则用这个方法修改成正确的
        if (this.matcher(para.getParagraphText()).find()) {
            runs = para.getRuns();
            for (int i = 0; i < runs.size(); i++) {
                XWPFRun run = runs.get(i);
                if (fontSize == 0) {
                    fontSize = run.getFontSize();
                }
                if (underlinePatterns == null) {
                    underlinePatterns = run.getUnderline();
                }
                String runText = run.toString();
                matcher = this.matcher(runText);
                if (matcher.find()) {
                    while ((matcher = this.matcher(runText)).find()) {
                        runText = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
                    }
                    //直接调用XWPFRun的setText()方法设置文本时，在底层会重新创建一个XWPFRun，把文本附加在当前文本后面，
                    //所以我们不能直接设值，需要先删除当前run,然后再自己手动插入一个新的run。
                    para.removeRun(i);
                    para.insertNewRun(i).setText(runText);
                }
            }
        }
    }

    /**
     * 替换段落里面的变量
     *
     * @param para   要替换的段落
     * @param params 参数
     */
    private void replaceInParas(XWPFParagraph para, Map<String, Object> params) {
        List<XWPFRun> runs;
        Matcher matcher;
        String runText = "";
        int fontSize = 0;
        UnderlinePatterns underlinePatterns = null;
        if (matcher(para.getParagraphText()).find()) {
            runs = para.getRuns();
            if (runs.size() > 0) {
                int j = runs.size();
                for (int i = 0; i < j; i++) {
                    XWPFRun run = runs.get(0);

                    if (fontSize == 0) {
                        fontSize = run.getFontSize();
                    }
                    if (underlinePatterns == null) {
                        underlinePatterns = run.getUnderline();
                    }
                    String i1 = run.toString();
                    runText += i1;
                    para.removeRun(0);
                }
            }
            matcher = matcher(runText);
            if (matcher.find()) {
                while ((matcher = matcher(runText)).find()) {
                    runText = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
                }
                //直接调用XWPFRun的setText()方法设置文本时，在底层会重新创建一个XWPFRun，把文本附加在当前文本后面，
                //所以我们不能直接设值，需要先删除当前run,然后再自己手动插入一个新的run。
                //para.insertNewRun(0).setText(runText);//新增的没有样式
                //TODO 换行建议使用SpecialCharUtil.docxChangeLing
                XWPFRun run = para.createRun();

                setFont(run, runText);
                //run.setUnderline(underlinePatterns);

                if (runText.indexOf("关键字符自定义样式/格式") == 0) {
                    run.setFontFamily("方正小标宋简体");
                    run.setFontSize(22);
                    run.setBold(true);
                }
//                else {
//                    run.setFontFamily("仿宋");
//                    run.setFontSize(16);
//                    run.setBold(true);
//                }

                //run.setFontFamily("仿宋");//字体
                //run.setFontSize(16);//字体大小
                //run.setBold(true); //加粗
                //run.setColor("FF0000");
                //默认：宋体（wps）/等线（office2016） 5号 两端对齐 单倍间距
                //run.setBold(false);//加粗
                //run.setCapitalized(false);//我也不知道这个属性做啥的
                //run.setCharacterSpacing(5);//这个属性报错
                //run.setColor("BED4F1");//设置颜色--十六进制
                //run.setDoubleStrikethrough(false);//双删除线
                //run.setEmbossed(false);//浮雕字体----效果和印记（悬浮阴影）类似
                //run.setFontFamily("宋体");//字体
                //run.setFontFamily("华文新魏", FontCharRange.cs);//字体，范围----效果不详
                //run.setFontSize(14);//字体大小
                //run.setImprinted(false);//印迹（悬浮阴影）---效果和浮雕类似
                //run.setItalic(false);//斜体（字体倾斜）
                //run.setKerning(1);//字距调整----这个好像没有效果
                //run.setShadow(true);//阴影---稍微有点效果（阴影不明显）
                //run.setSmallCaps(true);//小型股------效果不清楚
                //run.setStrike(true);//单删除线（废弃）
                //run.setStrikeThrough(false);//单删除线（新的替换Strike）
                //run.setSubscript(VerticalAlign.SUBSCRIPT);//下标(吧当前这个run变成下标)---枚举
                //run.setTextPosition(20);//设置两行之间的行间距
                //run.setUnderline(UnderlinePatterns.DASH_LONG);//各种类型的下划线（枚举）
                //run0.addBreak();//类似换行的操作（html的  br标签）
                //run0.addTab();//tab键
                //run0.addCarriageReturn();//回车键
                //注意：addTab()和addCarriageReturn() 对setText()的使用先后顺序有关：比如先执行addTab,再写Text这是对当前这个Text的Table，反之是对下一个run的Text的Tab效果
            }
        }
    }

    /**
     * 设置字体型号、大小
     *
     * @param run
     * @param runText
     */
    private void setFont(XWPFRun run, String runText) {
        run.setFontFamily("仿宋_GB2312");//字体
        run.setFontSize(16);//字体大小
        run.setText(runText);

    }

    /**
     * 合并runs中的内容
     *
     * @param para
     * @return
     */
    private List<XWPFRun> replaceText(XWPFParagraph para) {
        List<XWPFRun> runs = para.getRuns();
        String str = "";
        boolean flag = false;
        for (int i = 0; i < runs.size(); i++) {
            XWPFRun run = runs.get(i);
            String runText = run.toString();
            if (flag || runText.equals("${")) {
                str = str + runText;
                flag = true;
                para.removeRun(i);
                if (runText.equals("}")) {
                    flag = false;
                    para.insertNewRun(i).setText(str);
                    str = "";
                }
                i--;
            }
            if (runText.contains("</br>")) {
                run.addBreak();

            }
        }

        return runs;
    }

    /**
     * 替换表格里面的变量
     *
     * @param doc    要替换的文档
     * @param params 参数
     */
    private void replaceInTable(XWPFDocument doc, Map<String, Object> params) {
        Iterator<XWPFTable> iterator = doc.getTablesIterator();
        XWPFTable table;
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        while (iterator.hasNext()) {
            table = iterator.next();
            rows = table.getRows();
            for (XWPFTableRow row : rows) {
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    paras = cell.getParagraphs();
                    for (XWPFParagraph para : paras) {
                        this.replaceInPara(para, params);
                    }
                }
            }
        }
    }

    /**
     * 写入图片在word中
     *
     * @param document
     * @throws IOException
     */
    private void insertImage(CustomXWPFDocument document, String key, String path) throws Exception {
        //图片
        if (path.equals("") || path.length() == 0) {
            return;
        }
        FileInputStream in = new FileInputStream(path);
        //段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (text != null) {
                text = text.trim();
                if (text.equals(key)) {
                    List<XWPFRun> runs = paragraph.getRuns();
                    for (int i = 0; i < runs.size(); i++) {
                        //替换模板原来位置
                        if (runs.get(i).toString().indexOf(key) != -1) {
                            runs.get(i).setText(text.replace(key, ""), 0);
                            byte[] ba = new byte[in.available()];
                            int len = in.read(ba);
                            ByteArrayInputStream byteInputStream = new ByteArrayInputStream(ba, 0, len);
                            //设置图片
                            document.addPictureData(byteInputStream, XWPFDocument.PICTURE_TYPE_PNG);
                            //创建一个word图片，并插入到文档中-->像素可改
                            document.createPicture(document.getAllPictures().size() - 1, 500, 260, paragraph);
                        }
                        break;
                    }
                    break;
                }
            }
        }
    }

    /**
     * 写入图片在word中
     *
     * @param document
     * @throws IOException
     */
    private void replaceNotR(CustomXWPFDocument document, String key) throws Exception {
        //段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (text != null) {
                text = text.trim();
                if (text.equals(key)) {
                    List<XWPFRun> runs = paragraph.getRuns();
                    for (int i = 0; i < runs.size(); i++) {
                        //替换模板原来位置
                        if (runs.get(i).toString().indexOf(key) != -1) {
                            runs.get(i).setText(text.replace(key, ""), 0);
                        }
                        break;
                    }
                    break;
                }
            }
        }
    }

    /**
     * 正则匹配字符串
     *
     * @param str
     * @return
     */
    private Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    /**
     * 关闭输入流
     *
     * @param is
     */
    private void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭输出流
     *
     * @param os
     */
    private void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
